const express = require('express');
const cors = require('cors');
require('dotenv').config();
const cluster = require('cluster');
const logger = require('../../utils/logger');
const os = require('os');
const timeoutHandler = require('express-timeout-handler');
///////////
const {Script} = require("vm");
const {browser} = require('../../');
const jsdom = require('sdenv-jsdom');
const cheerio = require('cheerio');
const {JSDOM, CookieJar} = jsdom;

jsdomFromTextWithCookie = (config, cookieJar) => {
    const virtualConsole = new jsdom.VirtualConsole();
    virtualConsole.sendTo({
        log: logger.log.bind(logger),
        warn: logger.warn.bind(logger),
        error: logger.error.bind(logger),
    });
    if (!cookieJar) cookieJar = new CookieJar();
    const options = {
        pretendToBeVisual: true,
        cookieJar,
        virtualConsole,
        ...config,
    }
    return [(text) => {
        return new JSDOM(text, options);
    }, cookieJar];
}

function loadPages(url, htmltext, jstext, cookie_obj, res, startTime) {
    const url_obj = new URL(url);
    const baseUrl = url_obj.protocol + '//' + url_obj.hostname;
    let defaultCookieJar = new CookieJar();
    let cookie_str = '';
    Object.keys(cookie_obj).forEach(key => {
        cookie_str += `${key}=${cookie_obj[key]}; `
    });
    if (cookie_str) {
        defaultCookieJar.setCookieSync(cookie_str, baseUrl);
    }
    const [jsdomer, cookieJar] = jsdomFromTextWithCookie({
        url: `${url}`,
        referrer: `${url}`,
        contentType: "text/html",
        runScripts: "outside-only",
        userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
    }, defaultCookieJar);
    const dom = jsdomer(htmltext);

    const $ = cheerio.load(htmltext);
    const scripts = [...$('script')]
    const tsscript = scripts.map(ele => $(ele).text()).filter(text => text.includes('$_ts.nsd') && text.includes('$_ts.cd'));
    let window = dom.window;
    window.$_ts = Function('window', tsscript[0] + 'return $_ts')({});
    window.onbeforeunload = async (url) => {
        const cookies_str = cookieJar.getCookieStringSync(baseUrl);
        let cookies = cookies_str.split('; ').reduce((acc, cur) => {
            const [key, value] = cur.split('=');
            acc[key] = value;
            return acc;
        }, {});
        logger.info(`生成cookie(${new Date().getTime() - startTime}ms):`, cookies);
        window.close();
        res.status(200).json({
            "code": 200,
            "data": cookies,
            "cost": `${new Date().getTime() - startTime}ms`,
            "msg": "调用成功"
        })
    }
    browser(window, 'chrome');
    new Script(jstext).runInContext(dom.getInternalVMContext());
}
///////////


const app = express();
const PORT = process.env.PORT || 53000;
// Node.js是单线程的，但可以使用集群模式来利用多核CPU。可以使用cluster模块来实现
if (cluster.isMaster) {
    const numCPUs = os.cpus().length;
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    cluster.on('exit', (worker, code, signal) => {
        console.log(`Worker ${worker.process.pid} died`);
        cluster.fork();
    });
} else {
    // Middleware
    app.use(cors());
    app.use(express.json({ limit: '50mb' })); // Adjust limit as needed
    app.use(express.urlencoded({ limit: '50mb', extended: true }));

    // Timeout configuration
    const timeoutOptions = {
        timeout: 5000, // 5 seconds
        onTimeout: (req, res) => {
            res.status(503).json({
                    "code": 503,
                    "data": null,
                    "cost": `5000ms`,
                    "msg": `Service unavailable. Please retry later.`
                });
        },
        onDelayedResponse: (req, method, args, requestTime) => {
            console.warn(`Request for ${req.url} exceeded timeout of ${requestTime}ms`);
        },
        disable: ['write', 'setHeaders', 'send', 'json', 'end'],
    };

    app.use(timeoutHandler.handler(timeoutOptions));

    // Routes
    app.get('/', (req, res) => {
        res.send('Hello World');
    });

    app.post('/rs6', (req, res) => {
        const startTime = new Date().getTime();
        const url = req.body.url;
        const htmltext = req.body.htmltext;
        const jstext = req.body.jstext;
        const cookie_dict = req.body.cookie_dict;
        logger.debug(`url -> ${url}`);
        logger.debug(`cookie_dict -> ${JSON.stringify(cookie_dict)}`);
        try {
            loadPages(url, htmltext, jstext, cookie_dict, res, startTime);
        } catch (error) {
            logger.error(error);
            res.status(400).json({
                "code": 400,
                "data": null,
                "cost": `${new Date().getTime() - startTime}ms`,
                "msg": `Invalid request ${error}`
            });
        }
    });

    app.listen(PORT, () => {
        console.log(`Worker ${process.pid} is running on port ${PORT}`);
    });
}
